package com.hhoss.util.thread;

import com.hhoss.lang.ClassMeta;
import com.hhoss.logs.Logger;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author kejun
 * @see java.util.concurrent.Executors.DefaultThreadFactory
 */
public final class ThreadPool extends ThreadPoolExecutor implements RejectedExecutionHandler{
	private static final Logger logger = Logger.get();
	private static final Map<String,ThreadPool> workerMap=new HashMap<>();
	private static final int DEF_SIZE = 8;
	
	/**
	 * @param name the pool key name
	 * @param size the pool size ,allow concurrent executing 
	 * @param queue the size of BlockingQueue, max count in queue
	 * @return ExecutorService
	 */
	protected ThreadPool(String name, int size, int queue) {
		//super(size,1000,alive, TimeUnit.SECONDS, size<=1<<4?new LinkedBlockingQueue<Runnable>():new SynchronousQueue<Runnable>(), getFactory(name));
		super(size, size, 90, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queue), getFactory(name));
		setMaximumPoolSize((int)Math.sqrt(size*queue));
		setRejectedExecutionHandler(this);
		logger.debug("Pool[{}] created: core={},maxi={};queue={}",name,getCorePoolSize(),getMaximumPoolSize(),getQueue().getClass().getSimpleName());
		workerMap.put(name, this);
	}
	
	public ThreadPool(){
		this(ClassMeta.caller().getSimpleName(),DEF_SIZE, DEF_SIZE*1000);
		// Executors.newCachedThreadPool();
		// Executors.newFixedThreadPool(DEF_SIZE);
	}
	   
	@Override
	protected void beforeExecute(Thread t, Runnable r) { 
		ThreadHold.clear();//clear thread old variables;
		logger.trace("before[{}], Thread[{}]Pool: total={},active={};count={},queue={}",r,t.getName(),getPoolSize(),getActiveCount(),getTaskCount(),getQueue().size());
		super.beforeExecute(t,r);
	}
	
	@Override
	protected void afterExecute(Runnable r, Throwable t) {
		super.afterExecute(r,t);
		logger.trace("finish[{}], ThreadPool: total={},active={};count={},queue={}",r,getPoolSize(),getActiveCount(),getTaskCount(),getQueue().size());
		ThreadHold.clear();//clear current thread variables;
	}

	@Override
	protected void terminated() {
		logger.info("terminated, ThreadPool: total={},active={};count={},queue={}", getPoolSize(),getActiveCount(),getTaskCount(),getQueue().size());
		super.terminated();
	}

	@Override
	public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
       String msg = "ThreadPool[{}] is EXHAUSTED! size:{}, active:{}, core:{}, max:{}, largest:{}, tasks:{}, completed:{}. Executor isShutdown:{}, isTerminated:{}, isTerminating:{}!"; 
       logger.error(msg, getPoolKey(),e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
                e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating());
	}
	
	public String getPoolKey(){
		for(Entry<String,ThreadPool> ent : workerMap.entrySet()){
			if(this==ent.getValue()){
				return ent.getKey();
			}
		}
		return "unmanagedPool";
	}
	
	@Override
    public void execute(Runnable runnable) {
		Class<?>[] clazz= ClassMeta.callers();
		for(int i=1;i<clazz.length;i++){
			if(!clazz[i].isInstance(this)){
				logger.debug("to async execute for {}",clazz[i].getName() );
				break;
			}
		}
    	super.execute(TaskCall.get(runnable));
    }
	
	/**
	 * run in a generic pool for multi thread
	 * @param object to execute instance 
	 * @param method name of executed method 
	 * @param args parameters of the method 
	 */	
	public Future<?> execute(Object object, String method, Object... args) {
		return (Future<?>)submit(TaskCall.get(object, method,args));
	}
	
	/**
	 * @param name the pool suffix name
	 * @param size the pool core size
	 * @return ExecutorService 
	 */
	public static ExecutorService get(String name, int size){
		return getWorker(name,size);
	}
	
	/**
	 * @param name the pool suffix name
	 * @return ExecutorService with core size {@link #DEF_SIZE};
	 */
	public static ExecutorService get(String name){
		return getWorker(name,DEF_SIZE);
	}
	
	/**
	 * allow remove idle core threads after seconds
	 * @param secs
	 * @return this
	 */
	public ThreadPool letAlive(int secs){
		setKeepAliveTime(secs,TimeUnit.SECONDS);
		allowCoreThreadTimeOut(true);
		return this;
	}
	
	/**
	 * set core threads for concurrent running
	 * @param size of concurrent threads
	 * @return this
	 */
	public ThreadPool letAllow(int size){
		setCorePoolSize(size);
		return this;
	}
	
	/**
	 * set the final threads max concurrent counts, not include the queue
	 * @param size of final max concurrent threads, count(first+final)
	 * @return this
	 */
	public ThreadPool letCount(int size){
		setMaximumPoolSize(size);
		return this;
	}
	
	/**
	 * @return ExecutorService with name of caller's className and core size {@link #DEF_SIZE}; 
	 */
	public static ExecutorService get(){
		return getWorker(DEF_SIZE);
	}
	
	/**
	 * @param size  core pool size
	 * @return ExecutorService  with name of caller's className
	 */
	public static ExecutorService get(int size){
		return getWorker(size);
	}
	
	/**
	 * @param size the core pool size
	 * @return ExecutorService with caller's className
	 */
	private static ThreadPool getWorker(int size){
		String name = ClassMeta.caller(2).getSimpleName();
		return getWorker(name,size);
	}
	
	/**
	 * @param name the pool suffix name
	 * @param size the core pool size
	 * @return ExecutorService with name
	 */
	private static ThreadPool getWorker(String name, int size){
		ThreadPool es = workerMap.get(name);
		return(es==null)?new ThreadPool(name,size,size*1000):es;
	}
	
	/**
	 * run in a generic pool for multi thread
	 * @param runnable
	 */
	public static void run(Runnable runnable) {
		getWorker(DEF_SIZE).execute(TaskCall.get(runnable));
	}
	
	/**
	 * run in a generic pool for multi thread
	 * @param runnable
	 */
	public static <V> Future<V> run(Callable<V> callable) {
		return getWorker(DEF_SIZE).submit(TaskCall.get(callable));
	}
	
	/**
	 * run in a generic pool for multi thread
	 * @param object to execute instance 
	 * @param method name of executed method 
	 * @param args parameters of the method 
	 * @return Future
	 */
	public static Future<?> run(Object object, String method, Object... args) {
		return getWorker(DEF_SIZE).submit(TaskCall.get(object, method,args));
	}	
	
	private static ThreadFactory getFactory(String name){
		String key = (name==null||name.trim().length()==0)?"work":name.trim();
		return new ThreadFactory("pool%d-"+key+"-");
	}

}

